/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.catalina.startup;


import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;


/**
 * General purpose wrapper for command line tools that should execute in an environment with the common class loader
 * environment set up by Catalina. This should be executed from a command line script that conforms to the following
 * requirements:
 * <ul>
 * <li>Passes the <code>catalina.home</code> system property configured with the pathname of the Tomcat installation
 * directory.</li>
 * <li>Sets the system classpath to include <code>bootstrap.jar</code> and <code>$JAVA_HOME/lib/tools.jar</code>.</li>
 * </ul>
 * <p>
 * The command line to execute the tool looks like:
 *
 * <pre>
 *   java -classpath $CLASSPATH org.apache.catalina.startup.Tool \
 *     ${options} ${classname} ${arguments}
 * </pre>
 * <p>
 * with the following replacement contents:
 * <ul>
 * <li><strong>${options}</strong> - Command line options for this Tool wrapper. The following options are supported:
 * <ul>
 * <li><em>-ant</em> : Set the <code>ant.home</code> system property to corresponding to the value of
 * <code>catalina.home</code> (useful when your command line tool runs Ant).</li>
 * <li><em>-common</em> : Add <code>common/classes</code> and <code>common/lib</code> to the class loader
 * repositories.</li>
 * <li><em>-server</em> : Add <code>server/classes</code> and <code>server/lib</code> to the class loader
 * repositories.</li>
 * <li><em>-shared</em> : Add <code>shared/classes</code> and <code>shared/lib</code> to the class loader
 * repositories.</li>
 * </ul>
 * <li><strong>${classname}</strong> - Fully qualified Java class name of the application's main class.</li>
 * <li><strong>${arguments}</strong> - Command line arguments to be passed to the application's <code>main()</code>
 * method.</li>
 * </ul>
 */
public final class Tool {


    private static final Log log = LogFactory.getLog(Tool.class);

    // ------------------------------------------------------- Static Variables


    /**
     * Set <code>ant.home</code> system property?
     */
    private static boolean ant = false;


    /**
     * The pathname of our installation base directory.
     */
    private static final String catalinaHome = System.getProperty(Constants.CATALINA_HOME_PROP);


    /**
     * Include common classes in the repositories?
     */
    private static boolean common = false;


    /**
     * Include server classes in the repositories?
     */
    private static boolean server = false;


    /**
     * Include shared classes in the repositories?
     */
    private static boolean shared = false;


    // ----------------------------------------------------------- Main Program


    /**
     * The main program for the bootstrap.
     *
     * @param args Command line arguments to be processed
     */
    @SuppressWarnings("null")
    public static void main(String[] args) {

        // Verify that "catalina.home" was passed.
        if (catalinaHome == null) {
            log.error("Must set '" + Constants.CATALINA_HOME_PROP + "' system property");
            System.exit(1);
        }

        // Process command line options
        int index = 0;
        label:
        while (true) {
            if (index == args.length) {
                usage();
                System.exit(1);
            }
            switch (args[index]) {
                case "-ant":
                    ant = true;
                    break;
                case "-common":
                    common = true;
                    break;
                case "-server":
                    server = true;
                    break;
                case "-shared":
                    shared = true;
                    break;
                default:
                    break label;
            }
            index++;
        }

        // Set "ant.home" if requested
        if (ant) {
            System.setProperty("ant.home", catalinaHome);
        }

        // Construct the class loader we will be using
        ClassLoader classLoader = null;
        try {
            List<File> packed = new ArrayList<>();
            List<File> unpacked = new ArrayList<>();
            unpacked.add(new File(catalinaHome, "classes"));
            packed.add(new File(catalinaHome, "lib"));
            if (common) {
                unpacked.add(new File(catalinaHome, "common" + File.separator + "classes"));
                packed.add(new File(catalinaHome, "common" + File.separator + "lib"));
            }
            if (server) {
                unpacked.add(new File(catalinaHome, "server" + File.separator + "classes"));
                packed.add(new File(catalinaHome, "server" + File.separator + "lib"));
            }
            if (shared) {
                unpacked.add(new File(catalinaHome, "shared" + File.separator + "classes"));
                packed.add(new File(catalinaHome, "shared" + File.separator + "lib"));
            }
            classLoader = ClassLoaderFactory.createClassLoader(unpacked.toArray(new File[0]),
                    packed.toArray(new File[0]), null);
        } catch (Throwable t) {
            Bootstrap.handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
        Thread.currentThread().setContextClassLoader(classLoader);

        // Load our application class
        Class<?> clazz = null;
        String className = args[index++];
        try {
            if (log.isDebugEnabled()) {
                log.debug("Loading application class " + className);
            }
            clazz = classLoader.loadClass(className);
        } catch (Throwable t) {
            Bootstrap.handleThrowable(t);
            log.error("Exception creating instance of " + className, t);
            System.exit(1);
        }

        Method method = null;
        String[] params = new String[args.length - index];
        System.arraycopy(args, index, params, 0, params.length);
        try {
            if (log.isTraceEnabled()) {
                log.trace("Identifying main() method");
            }
            String methodName = "main";
            Class<?>[] paramTypes = new Class[1];
            paramTypes[0] = params.getClass();
            method = clazz.getMethod(methodName, paramTypes);
        } catch (Throwable t) {
            Bootstrap.handleThrowable(t);
            log.error("Exception locating main() method", t);
            System.exit(1);
        }

        // Invoke the main method of the application class
        try {
            if (log.isTraceEnabled()) {
                log.trace("Calling main() method");
            }
            Object[] paramValues = new Object[1];
            paramValues[0] = params;
            method.invoke(null, paramValues);
        } catch (Throwable t) {
            Throwable throwable = Bootstrap.unwrapInvocationTargetException(t);
            Bootstrap.handleThrowable(throwable);
            log.error("Exception calling main() method", throwable);
            System.exit(1);
        }

    }


    /**
     * Display usage information about this tool.
     */
    private static void usage() {

        log.info("Usage:  java org.apache.catalina.startup.Tool [<options>] <class> [<arguments>]");

    }


}
